package edu.udo.sopra10.chaturaji.entities.pieces;

import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNull;
import static edu.udo.sopra10.chaturaji.util.ExceptionUtil.assertNotNullOrContainsNull;
import static java.util.Collections.emptyList;
import static java.util.Collections.unmodifiableCollection;
import static org.apache.commons.lang.builder.ToStringStyle.SHORT_PREFIX_STYLE;

import java.util.Collection;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

import edu.udo.sopra10.chaturaji.entities.Move;
import edu.udo.sopra10.chaturaji.entities.PieceMove;
import edu.udo.sopra10.chaturaji.entities.Player;
import edu.udo.sopra10.chaturaji.entities.Square;
import edu.udo.sopra10.chaturaji.entities.coordinates.Position;
import edu.udo.sopra10.chaturaji.util.SimpleClonable;

/**
 * A single piece on the board. This class implements shared logic for all
 * pieces, especially the calculation of possible moves and captures.
 * 
 * @author Simon Dierl
 */
public abstract class Piece extends SimpleClonable {
	/**
	 * The movements and captures a piece an make in a given position.
	 * 
	 * @author Simon Dierl
	 */
	public class Actions {
		/**
		 * The boat-triumphing move a piece can make.
		 */
		private final Position possibleBoatTriumph;

		/**
		 * The capturing moves the piece can make.
		 */
		private final Collection<Position> possibleCaptures;

		/**
		 * The non-capturing moves the piece can make.
		 */
		private final Collection<Position> possibleMoves;

		/**
		 * The pawn-promoting moves a piece can make.
		 */
		private final Collection<Position> possiblePawnPromotions;

		/**
		 * Collect a set of actions in a new object.
		 * 
		 * @param possibleMoves
		 *            the possible moves.
		 * @param possibleCaptures
		 *            the possible captures.
		 * @param possibleBoatTriumph
		 *            the possible boat triumph.
		 * @param possiblePawnPromotions
		 *            the possible pawn promotions.
		 */
		public Actions(final Collection<Position> possibleMoves,
				final Collection<Position> possibleCaptures,
				final Position possibleBoatTriumph,
				final Collection<Position> possiblePawnPromotions) {
			this.possibleMoves = possibleMoves;
			this.possibleCaptures = possibleCaptures;
			this.possibleBoatTriumph = possibleBoatTriumph;
			this.possiblePawnPromotions = possiblePawnPromotions;
		}

		/**
		 * Get the {@link Position} to which the piece can move to achieve boat
		 * triumph. This square will simultaneously be listed in
		 * {@link #getPossibleMoves()} or {@link #getPossibleCaptures()}.
		 * 
		 * @return the position to which the piece can move to achieve boat
		 *         triumph. May be {@code null} to indicate that no such move is
		 *         possible.
		 */
		public Position getPossibleBoatTriumph() {
			return possibleBoatTriumph;
		}

		/**
		 * Get all {@link Position}s to which the piece can move while capturing
		 * a piece on this square. It might simultaneously achieve boat triumph
		 * or pawn promotion.
		 * 
		 * @return all positions to which the piece can move while making a
		 *         capturing move, unmodifiable, never {@code null}, never
		 *         contains {@code null} . Empty collection indicates no
		 *         possible capturing moves.
		 */
		public Collection<Position> getPossibleCaptures() {
			return possibleCaptures;
		}

		/**
		 * Get all {@link Position}s to which the piece can move without
		 * capturing a piece. It might, however, achieve boat triumph or pawn
		 * promotion.
		 * 
		 * @return all positions to which the piece can move, unmodifiable,
		 *         never {@code null}, never contains {@code null}. Empty
		 *         collection indicates no possible moves.
		 */
		public Collection<Position> getPossibleMoves() {
			return possibleMoves;
		}

		/**
		 * Get all {@link Position}s to which the piece can move to achieve pawn
		 * promotion. These squares will simultaneously be listed in
		 * {@link #getPossibleMoves()} or {@link #getPossibleCaptures()}.
		 * 
		 * @return all positions to which the piece can move to achieve pawn
		 *         promotion, unmodifiable, never {@code null}, never contains
		 *         {@code null} . Empty collection indicates no possible pawn
		 *         promoting moves.
		 */
		public Collection<Position> getPossiblePawnPromotions() {
			return possiblePawnPromotions;
		}
	}

	public int getPieceValue() {
		return pieceValue;
	}

	public void setPieceValue(int pieceValue) {
		this.pieceValue = pieceValue;
	}

	/**
	 * The moves in which this piece was moved, in chronological order. This
	 * does not include capture of this piece.
	 */
	private final List<PieceMove> moves = new LinkedList<PieceMove>();

	/**
	 * The score gained for capturing this piece. This property is static to the
	 * class extending {@link Piece} and must not be modified.
	 */
	private final byte score;

	/**
	 * The player to who the piece belongs.
	 */
	protected Player owner;

	/**
	 * 
	 */
	protected int pieceValue;
	/**
	 * The square this piece is standing on, {@code null} indicates a captured
	 * piece.
	 */
	protected Square square;

	/**
	 * Create a new piece with a fixed score.
	 * 
	 * @param score
	 *            the score awarded to a player capturing this piece, must be
	 *            {@code >= 0}.
	 * @param owner
	 *            the player owning this piece, must not be {@code null}.
	 * @param square
	 *            the square the piece stands on, may be {@code null} to
	 *            indicate a captured piece.
	 */
	protected Piece(final byte score, final Player owner, final Square square) {
		if (owner == null) {
			throw new NullPointerException("owner must be non-null");
		}
		this.score = score;
		setOwner(owner);
		setSquare(square);
	}

	/**
	 * Append a new {@link PieceMove} in which this piece was moved to the
	 * chronological list of such moves. The move must be the newest move made
	 * by this piece.
	 * 
	 * @param move
	 *            the new move in which this piece was moved, must not be
	 *            {@code null}.
	 * @return if the move was new to the collection. Always returns
	 *         {@code true}.
	 */
	public boolean addMove(final PieceMove move) {
		return moves.add(assertNotNull(move, "move"));
	}

	/**
	 * Append several new {@link Move}s in which this piece was moved to the
	 * chronological list of such moves. The moves most be the newest moves made
	 * by this piece, and their internal ordering must also be chronological.
	 * 
	 * @param moves
	 *            the new moves in which this piece was moved, must not be
	 *            {@code null} or contain {@code null}.
	 * @return if any of the moves was new to the collection. Always returns
	 *         {@code !moves.isEmpty()}.
	 */
	public boolean addMoves(final List<PieceMove> moves) {
		return this.moves.addAll(assertNotNullOrContainsNull(moves, "moves"));
	}

	/**
	 * Remove all {@link Move}s from the collection of this piece's moves.
	 */
	public void clearMoves() {
		moves.clear();
	}

	@Override
	public boolean equals(final Object obj) {
		if (obj == this) {
			return true;
		} else if ((obj == null) || (getClass() != obj.getClass())) {
			return false;
		} else {
			final Piece piece = (Piece) obj;
			return new EqualsBuilder().append(getClass(), piece.getClass())
					.append(owner, piece.owner).append(score, piece.score)
					.append(square, piece.square).isEquals();
		}
	}

	/**
	 * Get all {@link Move}s in which the player moved this piece.
	 * 
	 * @return all moves in which this pieces was moves, never {@code null},
	 *         never contains {@code null}, always unmodifiable.
	 */
	public Collection<PieceMove> getMoves() {
		return unmodifiableCollection(moves);
	}

	/**
	 * Get this piece's owner.
	 * 
	 * @return the player owning this piece, never {@code null}.
	 */
	public Player getOwner() {
		return owner;
	}

	/**
	 * Get all {@link Actions} this piece may perform in its current position on
	 * its associated board. If the piece is captured, the actions are empty.
	 * 
	 * @return the actions that can be performed, never {@code null}.
	 */
	public Actions getPossibleActions() {
		if (square == null) {
			final Collection<Position> emptyPositions = emptyList();
			return new Actions(emptyPositions, emptyPositions, null,
					emptyPositions);
		}
		return getPossibleActions(square.getGame().getBoard(),
				square.getPosition());
	}

	/**
	 * Get all {@link Actions} this piece may perform in its current position on
	 * a specified board. If the piece is captured, the actions are empty.
	 * 
	 * @param board
	 *            the game board to use. This does NOT override this piece's
	 *            position. Must not be {@code null}.
	 * @param currentPosition
	 *            the position of this piece on the specified board. Must not be
	 *            {@code null}.
	 * @return the actions that can be performed, never {@code null}.
	 */
	public abstract Actions getPossibleActions(Square[][] board,
			Position currentPosition);

	/**
	 * Get the score awarded to a player who captures this piece.
	 * 
	 * @return the score this piece is worth, always {@code >= 0}.
	 */
	public byte getScore() {
		return score;
	}

	/**
	 * Get the {@link Square} this piece stands on. Also indicates a captured
	 * piece, which stand on no square.
	 * 
	 * @return the square this piece stands on. {@code null} indicates that the
	 *         piece was captured.
	 */
	public Square getSquare() {
		return square;
	}

	@Override
	public int hashCode() {
		return new HashCodeBuilder(91, 9).append(owner).append(score)
				.append(square).append(getClass()).toHashCode();
	}

	/**
	 * Remove a {@link Move} of this piece from the collection of such moves.
	 * 
	 * @param move
	 *            the move to remove, must not be {@code null}.
	 * @return if the move was contained in the collection.
	 */
	public boolean removeMove(final Move move) {
		return moves.remove(assertNotNull(move, "move"));
	}

	/**
	 * Remove several {@link Move}s of this piece from the collection of such
	 * moves.
	 * 
	 * @param moves
	 *            the moves to remove, must not be {@code null}. May contain
	 *            {@code null}, although this has no effect and is discouraged.
	 * @return if any of the moves were contained in the collection.
	 */
	public boolean removeMoves(final Collection<PieceMove> moves) {
		return this.moves.removeAll(assertNotNull(moves, "moves"));
	}

	/**
	 * Set this piece's owner.
	 * 
	 * @param owner
	 *            the new player owning this piece, must not be {@code null}.
	 */
	public void setOwner(final Player owner) {
		this.owner = assertNotNull(owner, "owner");
	}

	/**
	 * Set the {@link Square} this piece stands on or capture it.
	 * 
	 * @param square
	 *            the new square this piece stands on or {@code null} if the
	 *            piece was captured.
	 */
	public void setSquare(final Square square) {
		this.square = square;
	}

	@Override
	public String toString() {
		return new ToStringBuilder(this, SHORT_PREFIX_STYLE)
				.append("owner", owner).append("score", score)
				.append("square", square).toString();
	}

	/**
	 * Remove all {@link Move}s above a certain move number from the list of
	 * moves in which the piece was moved.
	 * 
	 * @param from
	 *            the lowest position to remove.
	 */
	public void truncateHistory(final int from) {
		moves.subList(from, moves.size()).clear();
	}
}
